﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation.Client;
using System.Web;
using System.Configuration;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Collections;
using Microsoft.TeamFoundation.Server;
using System.Web.Caching;
using ScrumDashboard.Library.Model;

namespace ScrumDashboard.Library.Tfs
{
    public partial class TfsManager
    {
        private TeamFoundationServer GetTfsServer()
        {
            if (_tfs != null)
                return _tfs;

            //Always cache TFSManager per web request for better performance
            if (HttpContext.Current != null && HttpContext.Current.Items.Contains(_cachedTFSManager))
            {
                _tfs = (TeamFoundationServer)HttpContext.Current.Items[_cachedTFSManager];
            }
            else
            {
                _tfs = new TeamFoundationServer(ConfigurationManager.AppSettings["tfsServer"]);
                if (HttpContext.Current != null)
                    HttpContext.Current.Items.Add(_cachedTFSManager, _tfs);
            }

            return _tfs;

        }

        private T GetService<T>()
        {
            T service = default(T);

            if (HttpContext.Current != null && HttpContext.Current.Items.Contains(typeof(T).ToString()))
            {
                service = (T)HttpContext.Current.Items[typeof(T).ToString()];
            }

            if (service == null)
            {
                service = (T)GetTfsServer().GetService(typeof(T));
                if (HttpContext.Current != null)
                    HttpContext.Current.Items.Add(typeof(T).ToString(), service);
            }

            return service;
        }

        /// <summary>
        /// Gets a list of display names for all users that are member of the project group Contributors
        /// </summary>
        /// <param name="tfsProject">The project in TFS</param>
        /// <returns>A list of users</returns>
        private Dictionary<string, User> GetProjectContributorsLookup(string tfsProject)
        {
            string cacheKey = tfsProject + "-GetProjectContributorsLookup";
            Dictionary<string, User> contributors = HttpRuntime.Cache[cacheKey] as Dictionary<string, User>;

            if (contributors == null)
            {
                contributors = new Dictionary<string, User>();
                WorkItemStore workItemStore = GetService<WorkItemStore>();
                Project project = workItemStore.Projects[tfsProject];

                IGroupSecurityService sec = GetService<IGroupSecurityService>();
                string groupName = String.Format("[{0}]\\Contributors", tfsProject);
                Identity contributorsGroup = sec.ReadIdentity(SearchFactor.AccountName, groupName, QueryMembership.Expanded);

                if (contributorsGroup == null)
                    throw new Exception("Could not read group: " + groupName);
                if (contributorsGroup.Members == null)
                    throw new Exception("Members of group " + groupName + " is NULL");

                foreach (string memberSid in contributorsGroup.Members)
                {
                    Identity memberInfo = sec.ReadIdentity(SearchFactor.Sid, memberSid, QueryMembership.None);
                    if (memberInfo.Type != IdentityType.WindowsUser || memberInfo.Deleted)
                        continue;

                    if (contributors.ContainsKey(memberInfo.DisplayName))
                        continue;

                    contributors.Add(memberInfo.DisplayName, new User(memberInfo.DisplayName, memberInfo.AccountName, memberInfo.Domain));
                }

                HttpRuntime.Cache.Add(cacheKey, contributors, null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, null);
            }

            return contributors;
        }

        /// <summary>
        /// Get the sprint backlog for a number of product backlogs
        /// </summary>
        /// <param name="productBacklog">A list of the product backlogs</param>
        /// <param name="sprintNumber">The number of the sprint</param>
        /// <returns>A list of Sprint Backlog items</returns>
        private List<SprintBacklogItem> CreateSprintViewBacklog(string tfsProject, int sprintNumber, string teamName, IList<ProductBacklogItem> productBacklog)
        {
            List<SprintBacklogItem> sprintBacklog = new List<SprintBacklogItem>();
            List<SprintBacklogItem> sprintBacklogRaw = GetSprintBacklog(tfsProject, sprintNumber);
            Dictionary<int, int> pbLookupCache = new Dictionary<int, int>();

            //Populate the lookup cache
            foreach (ProductBacklogItem pb in productBacklog)
            {
                foreach (int relatedSBI in pb.RelatedWorkItems)
                {
                    if (!pbLookupCache.ContainsKey(relatedSBI))
                    {
                        pbLookupCache.Add(relatedSBI, pb.Id);
                    }
                }
            }

            foreach (SprintBacklogItem sbi in sprintBacklogRaw)
            {
                if (pbLookupCache.ContainsKey(sbi.Id))
                {
                    sbi.ProductBackLogId = pbLookupCache[sbi.Id];
                    sprintBacklog.Add(sbi);
                }
                else if (String.IsNullOrEmpty(teamName) || String.Compare(teamName, sbi.Team, true) == 0)
                {
                    // We have an SBI assigned to the team in the current sprint but without
                    // a product backlog, lets figure out the parent and add it. This is to
                    // support product backlogs spanning multiple sprints (feature 2444)

                    int productBackLogId = FindProductBacklog(tfsProject, sbi);

                    ProductBacklogItem pb = productBacklog.Where(p => p.Id == productBackLogId).FirstOrDefault();

                    //If its new make sure we add it
                    if (pb == null && productBackLogId>0)
                    {
                        pb = GetProductBacklog(productBackLogId);
                        if (pb != null)
                        {
                            productBacklog.Add(pb);
                            //Populate the lookup cache
                            foreach (int relatedSBI in pb.RelatedWorkItems)
                            {
                                if (!pbLookupCache.ContainsKey(relatedSBI))
                                {
                                    pbLookupCache.Add(relatedSBI, pb.Id);
                                }
                            }
                        }
                    }

                    //If we got an PBI, lets add it
                    if (pb != null)
                    {
                        //Add SBI
                        sbi.ProductBackLogId = pb.Id;
                        sprintBacklog.Add(sbi);
                    }
                }
            }

            return sprintBacklog;
        }

        private int FindProductBacklog(string tfsProject, SprintBacklogItem sbi)
        {
            WorkItem wi = GetWorkItem(sbi.Id);
            LinkCollection links = wi.Links;
            foreach (Link link in links)
            {
                if (link.Comment == "Scrum2BugLink")
                    continue;
                if (!(link is RelatedLink))
                    continue;

                RelatedLink relLink = link as RelatedLink;
                WorkItem relWI = GetWorkItem(relLink.RelatedWorkItemId);
                if (String.Compare(relWI.Project.Name, tfsProject, true) == 0 && relWI.State != "Deleted" && (relWI.Type.Name == "Product Backlog Item" || (_viewBugsAsProductBacklogs && relWI.Type.Name == "Bug")))
                {
                    return relWI.Id;
                }
            }

            return 0;
        }

        /// <summary>
        /// Helper method to get all sprint backlog items for a specific sprint
        /// </summary>
        /// <param name="tfsProject"></param>
        /// <param name="sprintNumber"></param>
        /// <returns></returns>
        /// <remarks>Important! SBI in this list does look up PBI link for performance reasons</remarks>
        private List<SprintBacklogItem> GetSprintBacklog(string tfsProject, int sprintNumber)
        {
            string cacheKey = tfsProject + "-GetSprintBacklog-" + sprintNumber.ToString();
            List<SprintBacklogItem> items = HttpRuntime.Cache[cacheKey] as List<SprintBacklogItem>;
            if (items == null)
            {
                items = new List<SprintBacklogItem>();
                Hashtable context = new Hashtable();

                context.Add("project", tfsProject);
                context.Add("sprintNumber", sprintNumber);

                WorkItemStore workItemStore = GetService<WorkItemStore>();
                WorkItemCollection allItems = workItemStore.Query("SELECT " + ObjectFactory.SprintBacklogFields + " FROM WorkItems WHERE [System.TeamProject] = @project AND ([System.WorkItemType]='Sprint Backlog Item' OR [System.WorkItemType]='Bug' OR [System.WorkItemType]='Impediment') AND [System.IterationId]=@sprintNumber AND [System.State]<>'Deleted' ORDER BY [System.State] ASC,[Conchango.TeamSystem.Scrum.TaskPriority] ASC,[System.CreatedDate] ASC", context);

                for (int i = 0; i < allItems.Count; i++)
                {
                    WorkItem sbiSource = allItems[i];
                    SprintBacklogItem sbi = ObjectFactory.CreateSprintBacklogItem(sbiSource, GetUser(tfsProject, sbiSource.Fields["System.AssignedTo"].Value));
                    items.Add(sbi);
                }

                HttpRuntime.Cache.Add(cacheKey, items, new TfsCacheDependency(tfsProject), DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
            }
            return items;
        }
    }
}
